home *** CD-ROM | disk | FTP | other *** search
/ TPUG - Toronto PET Users Group / TPUG Users Group CD / TPUG Users Group CD.iso / AMIGA / AMICUS / AMICUS02.ADF / C / ar.c < prev    next >
C/C++ Source or Header  |  1989-05-30  |  40KB  |  1,421 lines

  1. /*
  2. From fnf@unisoft.UUCP Mon Dec  2 02:23:25 1985
  3. Path: gumby!uwvax!seismo!lll-crg!lll-lcc!unisoft!fnf
  4. Subject: portar.c (portable archiver)
  5. Date: 2 Dec 85 08:23:25 GMT
  6. Organization: UniSoft Systems, Berkeley
  7.  
  8.  
  9. It gets to be a real drag using "read" to transfer bunches of little
  10. files down to the AMIGA via the serial line.  The enclosed program
  11. helps ease the pain by allowing creation of a portable archive on
  12. one system, which can then be shipped to the other system for
  13. extraction.  (Text files only)
  14.  
  15. This is a highly modified version of the DECUS archiver that I pulled
  16. out of my musty archives.  It's several years old.  Enjoy.
  17.  
  18. */
  19.  
  20.  
  21. /*
  22.  *
  23.  *
  24.  * The  information  in  this  document  is  subject  to  change
  25.  * without  notice  and  should not be construed as a commitment
  26.  * by Digital Equipment Corporation or by DECUS.
  27.  *
  28.  * Neither Digital Equipment Corporation, DECUS, nor the authors
  29.  * assume any responsibility for the use or reliability of  this
  30.  * document or the described software.
  31.  *
  32.  *      Copyright (C) 1980, DECUS
  33.  *
  34.  *
  35.  * General permission to copy or modify, but not for profit,  is
  36.  * hereby  granted,  provided that the above copyright notice is
  37.  * included and reference made to  the  fact  that  reproduction
  38.  * privileges were granted by DECUS.
  39.  *
  40.  */
  41.  
  42. /*
  43.  *                      A R C H I V E
  44.  *
  45.  * Archiver, roughly from software tools.
  46.  *
  47.  */
  48.  
  49. /*
  50.  * title        ar      text file archiver
  51.  * index                text file archiver
  52.  * 
  53.  * synopsis
  54.  *      ar [-options] [-z logfile] archive_name file[s]
  55.  * 
  56.  * description
  57.  * 
  58.  *      Ar manages archives (libraries) of source files, allowing
  59.  *      a large number of small files to be stored without using
  60.  *      excessive system resources.  The following options may
  61.  *      be specified:
  62.  *
  63.  *              c       Force creation of new archive
  64.  *              d       Delete file from archive.
  65.  *              i       Insert, same as update
  66.  *              p       Print files on standard output
  67.  *              r       Replace, same as update
  68.  *              l       List archive contents (directory)
  69.  *              u       Update, same as replace
  70.  *              x       Extract named files
  71.  *              v       Verbose
  72.  *              z       Write verbose log to indicated file
  73.  *
  74.  *      The file name arguments may contain '*' and '?' wildcards, where
  75.  *      '*' matches any string of characters, and '?' matches one character.
  76.  *      ('%' may be used as a synonym for '?'.)  There is a slight, but
  77.  *      suble difference in the way wild cards are processed for the
  78.  *      various commands:
  79.  *
  80.  *              directory, delete, and extract
  81.  *
  82.  *      Match ('*' and '?') against the files in the
  83.  *      archive, performing the operation on all files that
  84.  *      match.  Except for delete, "no argument" matches
  85.  *      all files in the archive.
  86.  *
  87.  *              insert, replace, and update
  88.  *
  89.  *      Expand the wild-card arguments against the files
  90.  *      stored on the operating system -- eliminating all
  91.  *      wild cards.  Then, match against the archived
  92.  *      files.  Those that match are replaced by the
  93.  *      file on the operating system.  After replacement,
  94.  *      any additional files are appended to the archive.
  95.  *      Files in the archive that are not in the directory
  96.  *      are unchanged.
  97.  *
  98.  *      Currently, insert, replace, and update work the same.
  99.  *      If it seems reasonable, the program may be extended
  100.  *      as follows:
  101.  *
  102.  *              insert  Add files new only
  103.  *
  104.  *      Adds new files (not present in the archive)
  105.  *      but does not modify files currently in
  106.  *      the archive.  It would be an error to try
  107.  *      modifying a currently archived file.
  108.  *
  109.  *              replace Modify existing files only
  110.  *
  111.  *      Modify files present in the archive, but do
  112.  *      not add new files to the archive.
  113.  *
  114.  *              update  Modify existing, add new
  115.  *
  116.  *      This is simple to do, but would seem to be a rich
  117.  *      source of user error.
  118.  * 
  119.  * archive file format
  120.  * 
  121.  *      Archive files are standard text files.  Each archive element is
  122.  *      preceeded by a line of the format:
  123.  * 
  124.  *              -h-     file.name       date    true_name
  125.  *
  126.  *      Note that there is no line or byte count.  To prevent problems,
  127.  *      a '-' at the beginning of a record within a user file or embedded
  128.  *      archive will be "quoted" by doubling it.  The date and true filename
  129.  *      fields are ignored.  On Dec operating systems, file.name is
  130.  *      forced to lowercase.
  131.  * 
  132.  * diagnostics
  133.  * 
  134.  *      Diagnostic messages should be self-explanatory
  135.  * 
  136.  * author
  137.  * 
  138.  *      Martin Minow
  139.  *
  140.  *      Extensively reworked by Fred Fish, 1-Dec-85
  141.  *        - Reformatted using the "indent" program.
  142.  *        - Added support for macro based debugging package.
  143.  *        - Delinted and questionable C usages removed.
  144.  *        - Ported to Commodore AMIGA using Lattice C.
  145.  * 
  146.  */
  147.  
  148. #include <stdio.h>
  149.  
  150. #ifndef EOS
  151. #  define EOS '\000'
  152. #endif
  153.  
  154. #ifndef FALSE
  155. #  define FALSE (0)
  156. #endif
  157.  
  158. #ifndef TRUE
  159. #  define TRUE (1)
  160. #endif
  161.  
  162. /*
  163.  *      When calling Error(), the following flag bits define
  164.  *      optional processing.
  165.  */
  166.  
  167. #define WARN    (000001)                /* This is a warning message */
  168. #define ERR     (000002)                /* This is an error */
  169. #define FATAL   (000004)                /* Fatal, die after message */
  170. #define SYS     (000010)                /* System error number available */
  171.  
  172. /*
  173.  * The two routines fwild() and fnext() are faked on unix.
  174.  * Also faked on AMIGA for now, may do equivalent later.
  175.  */
  176.  
  177. #if unix || AMIGA
  178. #  define FAKEFWILD
  179. #endif
  180.  
  181. #ifdef FAKEFWILD
  182.   static FILE *fwild ();
  183.   static FILE *fnext ();
  184.   static void fgetname ();
  185. #else
  186.   extern  FILE *fwild ();               /* Wild card file lookup         */
  187.   extern  FILE *fnext ();               /* Open next wild card file      */
  188.   extern void fgetname ();
  189. #endif
  190.  
  191. #define TEMPNAME "ar.tmp"
  192.  
  193. /*
  194.  * arfile chains together strings of text.
  195.  */
  196.  
  197. typedef struct arfile {
  198.     struct arfile *l_next;      /* -> next list element          */
  199.     int l_flag;                 /* mark if found in archive      */
  200.     char *l_arname;             /* archive name argument         */
  201.     char *l_filename;           /* directory file name           */
  202. } ARFILE;
  203.  
  204. /*
  205.  * Global storage
  206.  */
  207.  
  208. FILE *arfd = NULL;              /* Archive                       */
  209. FILE *newfd = NULL;             /* New archive                   */
  210. FILE *logfd;                    /* Log output                    */
  211. char *logname;                  /* Name of log file              */
  212. int newarchive = FALSE;         /* True if create from scratch   */
  213. int logging = FALSE;            /* True if log file enabled      */
  214. char text[513];                 /* Working text                  */
  215. char arname[81];                /* Current archive member name   */
  216. char filename[81];              /* Working file name             */
  217. char arfilename[81];            /* Archive file name             */
  218. char *timetext;                 /* Time of day text              */
  219. int verbose = FALSE;            /* TRUE for verbosity            */
  220. int delflag = 0;                /* Delete files                  */
  221. int directory = 0;              /* Table of contents if lit      */
  222. int update = 0;                 /* Update files if lit           */
  223. int extract = 0;                /* Get files from archive        */
  224. int print = 0;                  /* Write files to stdout         */
  225. int errorflag = 0;              /* Set on fatal error            */
  226. ARFILE *list = NULL;            /* String list header            */
  227.  
  228. #ifdef unix
  229. #  define delete unlink
  230. #endif
  231.  
  232. /*
  233.  *      The following allow use on systems that don't have my macro based
  234.  *      debugging package.  The default, for now, is to assume it is not
  235.  *      available.   Fred Fish, 1-Dec-85
  236.  */
  237.  
  238. #ifdef DBUG
  239. #  include <local/dbug.h>
  240. #else   /* !DBUG */
  241. #  define DBUG_ENTER(a)
  242. #  define DBUG_RETURN(a) return(a)
  243. #  define DBUG_VOID_RETURN return
  244. #  define DBUG_2(a,b)
  245. #  define DBUG_3(a,b,c)
  246. #  define DBUG_4(a,b,c,d)
  247. #  define DBUG_5(a,b,c,d,e)
  248. #  define DBUG_PUSH(a)
  249. #endif  /* DBUG */
  250.  
  251. /*
  252.  *      Declare internal functions that are used before definition seen.
  253.  */
  254.  
  255. static char *GetTime ();        /* Get current time as printable ascii */
  256. static void Error ();           /* Process an error or warning */
  257. static void doupdate ();
  258. static void dodirectory ();
  259. static void dodelete ();
  260. static void doextract ();
  261. static int replace ();
  262. static int expandargs ();
  263. static int findfiles ();
  264. static int savestring ();
  265. static void dumplist ();
  266. static void usage ();
  267. static void notfound ();
  268. static int gethdr ();
  269. static int findarg ();
  270. static int compare ();
  271. static int addfile ();
  272. static void argetname ();
  273. static void filemove ();
  274. static void arcopy ();
  275. static void arimport ();
  276. static void arexport ();
  277. static int match ();
  278. static int breakout ();
  279. static int match1 ();
  280.  
  281. /*
  282.  *      Library functions.
  283.  */
  284.  
  285. extern char *malloc ();
  286. extern char *strcpy ();
  287. extern char *strchr ();
  288. extern int delete ();
  289. extern void free ();
  290. extern void exit ();
  291.  
  292. #ifndef fflush                  /* Sometimes is a macro */
  293.   extern void fflush ();
  294. #endif
  295.  
  296. /*
  297.  *      Main entry point.  Note declaration is 'int' and a meaningful
  298.  *      value is actually returned.  Generally this becomes the exit
  299.  *      status for the parent process.
  300.  */
  301.  
  302. int main (argc, argv)
  303. int argc;                       /* Arg count                     */
  304. char *argv[];                   /* Arg vector                    */
  305. {
  306.     register int i;             /* Random counter                */
  307.     register char *argp;        /* Arg pointer                   */
  308.  
  309.     DBUG_ENTER ("main");
  310.     logfd = stderr;
  311.     logname = "stderr";
  312.     timetext = GetTime ();
  313.     for (i = 1; i < argc; i++) {
  314.         if ((argp = argv[i]) == NULL) {
  315.             continue;                   /* From log file writer          */
  316.         }
  317.         if (*argp == '-') {
  318.             /* 
  319.              * Process options
  320.              */
  321.             argv[i] = NULL;             /* Erase it from file stuff      */
  322.             while (*++argp != EOS) {
  323.                 switch (tolower (*argp)) {
  324.                     case '#':           /* Can not bundle with other args! */
  325.                         DBUG_PUSH (argp);
  326.                         argp = " ";     /* Trickery to terminate arg */
  327.                         break;
  328.                     case 'c': 
  329.                         newarchive = TRUE;
  330.                         break;
  331.                     case 'd':   /* Delete from archive   */
  332.                         delflag = 1;
  333.                         break;
  334.                     case 'p':   /* Print on stdout       */
  335.                         print = 1;
  336.                         break;
  337.                     case 'l':   /* List directory        */
  338.                         directory = 1;
  339.                         break;
  340.                     case 'h':   /* Explicit usage requested */
  341.                         usage ();
  342.                         exit (0);
  343.                         break;
  344.                     case 'i':   /* Insert                */
  345.                     case 'r':   /* Replace               */
  346.                     case 'u':   /* Update modified       */
  347.                         update = 1;
  348.                         break;
  349.                     case 'v':   /* Verbose               */
  350.                         verbose = 1;
  351.                         break;
  352.                     case 'x':   /* Extract               */
  353.                         extract = 1;
  354.                         break;
  355.                     case 'z':   /* Log file              */
  356.                         logname = argv[i + 1];
  357.                         if ((logfd = fopen (logname, "w")) == NULL) {
  358.                             Error (ERR|SYS|FATAL, "can't create logfile '%s'",
  359.                                 logname);
  360.                         }
  361.                         if (verbose) {
  362.                             fprintf (stderr, "writing log to %s\n", logname);
  363.                         }
  364.                         logging = TRUE;
  365.                         argv[i + 1] = NULL;
  366.                         break;
  367.                     default: 
  368.                         Error (WARN|FATAL,
  369.                                 "illegal option '%c', use -h for help",
  370.                                 *argp);
  371.                 }
  372.             }
  373.             argv[i] = NULL;     /* Erase argument        */
  374.         } else if (arfd == NULL && newfd == NULL) {     /* Not option */
  375.             /* 
  376.              * First file is the archive name
  377.              */
  378.             if (newarchive || (arfd = fopen (argp, "r")) == NULL) {
  379.                 DBUG_3 ("new", "opening '%s' as new archive", argp);
  380.                 if ((newfd = fopen (argp, "w")) == NULL) {
  381.                     Error (ERR|FATAL|SYS, "can't create archive '%s'", argp);
  382.                 } else {
  383.                     newarchive = TRUE;
  384.                     if (verbose) {
  385.                         fprintf (logfd, "Creating new archive '%s'\n", argp);
  386.                     }
  387.                 }
  388.             }
  389.             argv[i] = NULL;     /* Erase argument        */
  390.             (void) strcpy (arfilename, argp);
  391.         }
  392.     }
  393.     if (errorflag) {
  394.         Error (ERR|FATAL, "previous error prevents continuation");
  395.     }
  396.     if (!newarchive && arfd == NULL) {
  397.         Error (ERR|FATAL, "no archive file specified, use -h for help");
  398.     }
  399.     /* 
  400.      * Got all arguments.
  401.      */
  402.     if ((i = delflag + directory + extract + print + update) > 1) {
  403.         Error (ERR|FATAL, "illogical option combination");
  404.     } else if (i == 0) {
  405.         if (verbose) {
  406.             fprintf (logfd, "Update selected by default\n");
  407.         }
  408.         update = 1;
  409.     }
  410.     if (!newarchive && (delflag || update)) {
  411.         if ((newfd = fopen (TEMPNAME, "w")) == NULL) {
  412.             Error (ERR|FATAL|SYS, "can't create work file '%s'", TEMPNAME);
  413.         }
  414.     }
  415.     /* 
  416.      * Debugging verbosity.
  417.      */
  418.     if (verbose) {
  419.         fprintf (logfd, "You have selected:");
  420.         if (directory) fprintf (logfd, " directory");
  421.         if (delflag) fprintf (logfd, " delete");
  422.         if (extract) fprintf (logfd, " extract");
  423.         if (print) fprintf (logfd, " print");
  424.         if (update) fprintf (logfd, " update");
  425.         if (verbose) fprintf (logfd, " and verbosity");
  426.         fprintf (logfd, ".\nArchive file is \"%s\".\n", arfilename);
  427.     }
  428.     if (expandargs (argc, argv, update)) {
  429.         Error (WARN, "errors found in arg expansion");
  430.     }
  431.     if (newarchive && !update) {
  432.         fprintf (logfd, "Dummy archive created\n");
  433.         (void) fclose (newfd);
  434.     } else if (directory) {
  435.         dodirectory ();
  436.     } else if (delflag) {
  437.         dodelete ();
  438.     } else if (extract || print) {
  439.         doextract (print);
  440.     } else if (update) {
  441.         doupdate ();
  442.     } else {
  443.         Error (FATAL|WARN, "no command was provided, use -h for help");
  444.     }
  445.     DBUG_RETURN (0);
  446. }
  447.  
  448. /*
  449.  * Write a table of contents
  450.  */
  451.  
  452. static void dodirectory ()
  453. {
  454.     DBUG_ENTER ("dodirectory");
  455.     text[0] = EOS;
  456.     while (gethdr (arfd)) {
  457.         if (findarg (arname, (char *) NULL)) {
  458.             printf (text);
  459.         }
  460.         arcopy (arfd, (FILE *) NULL);   /* Skip file             */
  461.     }
  462.     DBUG_VOID_RETURN;
  463. }
  464.  
  465.  
  466. /*
  467.  * Delete named files -- gotta have a name list
  468.  */
  469.  
  470. static void dodelete ()
  471. {
  472.     register int ecount;
  473.  
  474.     DBUG_ENTER ("dodelete");
  475.     if (list == NULL) {
  476.         Error (ERR|FATAL, "delete by name only");
  477.     }
  478.     ecount = replace (arfd, newfd, FALSE, 0);
  479.     notfound ();
  480.     (void) fclose (arfd);
  481.     (void) fclose (newfd);
  482.     if (ecount == 0) {
  483.         filemove (TEMPNAME, arfilename);
  484.     } else {
  485.         Error (WARN, "errors prevent deletion of archive");
  486.         if (logging) {
  487.             fprintf (logfd, "Errors prevent deletion of archive\n");
  488.         }
  489.         if (delete (TEMPNAME) == -1) {
  490.             Error (WARN|SYS, "can't delete '%s'", TEMPNAME);
  491.         }
  492.     }
  493.     DBUG_VOID_RETURN;
  494. }
  495.  
  496.  
  497. /*
  498.  * Extract or print named files
  499.  */
  500.  
  501. static void doextract (printflag)
  502. int printflag;                  /* TRUE to print, FALSE to extract */
  503. {
  504.     register FILE *outfd;
  505.  
  506.     DBUG_ENTER ("doextract");
  507.     outfd = (printflag) ? stdout : NULL;
  508.     text[0] = EOS;
  509.     while (gethdr (arfd)) {
  510.         if (!findarg (arname, (char *) NULL)) {
  511.             if (verbose) {
  512.                 fprintf (logfd, "Skipping \"%s\"\n", arname);
  513.             }
  514.             arcopy (arfd, (FILE *) NULL);               /* Skip          */
  515.         } else {
  516.             if (outfd != stdout) {
  517.                 if ((outfd = fopen (arname, "w")) == NULL) {
  518.                     Error (ERR|SYS, "can't create '%s'", arname);
  519.                     if (logging) {
  520.                         fprintf (logfd, "Can't create \"%s\"\n", arname);
  521.                     }
  522.                     arcopy (arfd, (FILE *) NULL);
  523.                     continue;
  524.                 }
  525.             }
  526.             if (verbose) {
  527.                 fprintf (logfd, "Creating \"%s\"\n", arname);
  528.             }
  529.             arexport (arfd, outfd);
  530.             (void) fclose (outfd);
  531.             outfd = NULL;
  532.         }
  533.     }
  534.     DBUG_VOID_RETURN;
  535. }
  536.  
  537. /*
  538.  * Update existing files, add argv[1]..argv[argc-1] at end
  539.  */
  540.  
  541. static void doupdate ()
  542. {
  543.     register int ecount;
  544.     register ARFILE *lp;
  545.  
  546.     DBUG_ENTER ("doupdate");
  547.     ecount = 0;
  548.     if (!newarchive) {
  549.         DBUG_2 ("old", "update using existing archive");
  550.         ecount = replace (arfd, newfd, TRUE, 0);
  551.     }
  552.     for (lp = list; lp != NULL; lp = lp -> l_next) {
  553.         if (!lp -> l_flag) {
  554.             ecount += addfile (lp -> l_arname, lp -> l_filename, newfd,
  555.                                 ecount, "Added");
  556.             lp -> l_flag = TRUE;
  557.         }
  558.     }
  559.     if (newarchive) {
  560.         DBUG_2 ("new", "new archive, no need to copy temp archive");
  561.         (void) fclose (newfd);
  562.         if (ecount) {
  563.             Error (WARN, "completed with %d errors", ecount);
  564.             if (logging) {
  565.                 fprintf (stderr, "completed with %d errors\n", ecount);
  566.             }
  567.         }
  568.     } else {
  569.         DBUG_2 ("new", "copy temp archive to new archive");
  570.         (void) fclose (arfd);
  571.         (void) fclose (newfd);
  572.         if (ecount == 0) {
  573.             filemove (TEMPNAME, arfilename);
  574.         } else {
  575.             Error (WARN|SYS, "move of '%s' to '%s' supressed because of errors",
  576.                     TEMPNAME, arfilename);
  577.             if (logging) {
  578.                 fprintf (logfd,
  579.                         "Move of %s to %s supressed because of errors\n",
  580.                         TEMPNAME, arfilename);
  581.             }
  582.         }
  583.     }
  584.     DBUG_VOID_RETURN;
  585. }
  586.  
  587. /*
  588.  * Replace or delete files from the archive.  The updated archive
  589.  * is written to outfd.
  590.  */
  591.  
  592. static int replace (infd, outfd, updateflag, ecount)
  593. FILE *infd;             /* Reading files from here */
  594. FILE *outfd;            /* Writing files here */
  595. int updateflag;         /* TRUE to update, FALSE to remove */
  596. int ecount;
  597. {
  598.     DBUG_ENTER ("replace");
  599.     text[0] = EOS;              /* Signal gethdr initialization  */
  600.     while (gethdr (infd)) {
  601.         /* 
  602.          * We have a file, is it selected?
  603.          */
  604.         if (findarg (arname, filename)) {
  605.             if (updateflag) {
  606.                 ecount += addfile (arname, filename, outfd, ecount,
  607.                                    "Replaced");
  608.             }
  609.             arcopy (infd, (FILE *) NULL);
  610.         } else {
  611.             /* 
  612.              * Not selected for update, copy to the new archive
  613.              */
  614.             (void) fputs (text, outfd);
  615.             arcopy (infd, outfd);
  616.         }
  617.     }
  618.     DBUG_RETURN (ecount);
  619. }
  620.  
  621. /*
  622.  * Process the argv[] vector, building the argument list.
  623.  * Note: argv[1] is the first argument -- argv[0] is untouched and
  624.  * NULL entries in argv[] are ignored.
  625.  *
  626.  * If updateflag is TRUE, arguments are expanded against the file
  627.  * directory (using fwild/fnext).  If FALSE, they are used as is.
  628.  *
  629.  * Return TRUE if errors occurred.
  630.  */
  631.  
  632. static int expandargs (argc, argv, updateflag)
  633. int argc;               /* Number of arguments */
  634. char *argv[];           /* Arg vector */
  635. int updateflag;         /* TRUE to trigger file search */
  636. {
  637.     register int in;
  638.     register int eflag;
  639.  
  640.     DBUG_ENTER ("expandargs");
  641.     eflag = 0;
  642.     for (in = 1; in < argc; in++) {
  643.         if (argv[in] != NULL) {
  644.             if (updateflag) {
  645.                 eflag += findfiles (argv[in]);
  646.             } else {
  647.                 eflag += savestring (argv[in], (char *) NULL);
  648.             }
  649.         }
  650.     }
  651.     DBUG_RETURN (eflag != 0);
  652. }
  653.  
  654. /*
  655.  * Archive element names, do fwild lookup to expand wildcards where possible.
  656.  */
  657.  
  658. static int findfiles (fname)
  659. char *fname;
  660. {
  661.     register int i;
  662.     register FILE *fd;
  663.  
  664.     DBUG_ENTER ("findfiles");
  665.     if ((fd = fwild (fname, "r")) == NULL) {
  666.         Error (WARN|SYS, "can't open directory or file '%s'", fname);
  667.         if (logging) {
  668.             fprintf (stderr, "Can't open directory or wildcard file \"%s\"\n",
  669.                     fname);
  670.         }
  671.         DBUG_RETURN (1);
  672.     }
  673.     /* 
  674.      * Locate each file, then save archive and file names
  675.      */
  676.     for (i = 0; fnext (fd) != NULL; i++) {
  677.         argetname (fd, arname, filename);
  678.         savestring (arname, filename);
  679.     }
  680.     if (i == 0) {
  681.         Error (WARN, "no match for '%s'", fname);
  682.         if (logging) {
  683.             fprintf (stderr, "Warning, no match for \"%s\"\n", fname);
  684.         }
  685.         DBUG_RETURN (1);
  686.     } else if (verbose) {
  687.         fprintf (logfd, "%d file%s in your directory match%s \"%s\"\n",
  688.                 i,
  689.                 (i > 1) ? "s" : "",
  690.                 (i == 1) ? "es" : "",
  691.                 fname);
  692.         DBUG_RETURN (0);
  693.     }
  694.     DBUG_RETURN (0);
  695. }
  696.  
  697. /*
  698.  * Insert text into the list in sorted order (on datum).
  699.  * Warn (and fail on) duplicates.
  700.  */
  701.  
  702. static int savestring (datum, file)
  703. char *datum;                    /* Archive element name */
  704. char *file;                     /* May be NULL if not necessary */
  705. {
  706.     register ARFILE *next;
  707.     register ARFILE **prev;
  708.     register ARFILE *new;
  709.     char *ardatum;
  710.     char *arfile;
  711.     int comp;
  712.  
  713.     DBUG_ENTER ("savestring");
  714.     arfile = NULL;
  715.     if (file != NULL) {
  716.         arfile = (char *) malloc ((unsigned) (strlen (file) + 1));
  717.         if (arfile == NULL) {
  718.             Error (ERR|FATAL|SYS, "can't allocate any more memory");
  719.         }
  720.         (void) strcpy (arfile, file);
  721.     }
  722.     if ((ardatum = (char *) malloc ((unsigned) (strlen (datum) + 1))) == NULL
  723.             || (new = (ARFILE *) malloc (sizeof (ARFILE))) == NULL) {
  724.             Error (ERR|FATAL|SYS, "can't allocate any more memory");
  725.     }
  726.     (void) strcpy (ardatum, datum);
  727.     new -> l_flag = FALSE;
  728.     new -> l_arname = ardatum;
  729.     new -> l_filename = arfile;
  730.     prev = &list;
  731.     next = list;
  732.     while (next != NULL && (comp = compare (datum, next -> l_arname)) > 0) {
  733.         if (comp == 0) {
  734.             Error (WARN, "duplicate argument '%s'", datum);
  735.             if (arfile) {
  736.                 free (arfile);
  737.             }
  738.             free (ardatum);
  739.             free ((char *) new);
  740.             DBUG_RETURN (TRUE);
  741.         }
  742.         prev = &next -> l_next;
  743.         next = *prev;
  744.     }
  745.     *prev = new;
  746.     new -> l_next = next;
  747.     DBUG_RETURN (FALSE);
  748. }
  749.  
  750.  
  751. #ifdef DEADCODE         /* Not used, leftover from what?  (fnf) */
  752.  
  753. /*
  754.  * Dump archive name list -- used for debugging only
  755.  */
  756.  
  757. static void dumplist ()
  758. {
  759.     register ARFILE *lp;
  760.  
  761.     DBUG_ENTER ("dumplist");
  762.     if ((lp = list) == NULL) {
  763.         Error (WARN, "list is empty");
  764.     } else {
  765.         while (lp != NULL) {
  766.             fprintf (stderr, "%s, \"%s\"",
  767.                         (lp -> l_flag) ? "    found" : "not found",
  768.                         lp -> l_arname);
  769.             if (lp -> l_filename == NULL) {
  770.                 fprintf (stderr, "\n");
  771.             } else {
  772.                 fprintf (stderr, "%s\n", lp -> l_filename);
  773.             }
  774.             lp = lp -> l_next;
  775.         }
  776.     }
  777.     DBUG_VOID_RETURN;
  778. }
  779.  
  780. #endif  /* DEADCODE */
  781.  
  782. static char *documentation[] = {
  783.     "Usage: portar -cdhilpruvx archive files",
  784.     "",
  785.     "  c  Create a new archive",
  786.     "  d  Delete named files from archive",
  787.     "  h  Print this help info",
  788.     "  i  Insert named files into archive",
  789.     "  l  List archive directory",
  790.     "  p  Print named files on standard output",
  791.     "  r  Replace named files",
  792.     "  u  Update -- replace named files",
  793.     "  v  Verbose -- give running commentary",
  794.     "  x  Extract -- copy named files to current directory",
  795.     "  z  Put logfile in file named in next argument",
  796.     "",
  797.     "i, r, and u, are identical",
  798.     "",
  799.     NULL
  800. };
  801.  
  802. static void usage ()
  803. {
  804.     register char **dp;
  805.     
  806.     DBUG_ENTER ("usage");
  807.     for (dp = documentation; *dp != NULL; dp++) {
  808.         printf ("%s\n", *dp);
  809.     }
  810.     DBUG_VOID_RETURN;
  811. }
  812.  
  813. static void Error (flags, fmt, arg1, arg2, arg3)
  814. int flags;
  815. char *fmt;
  816. char *arg1;
  817. char *arg2;
  818. char *arg3;
  819. {
  820.     fprintf (stderr, "portar: ");
  821.     if (flags & WARN) {
  822.         fprintf (stderr, "warning -- ");
  823.     } else if (flags & ERR) {
  824.         fprintf (stderr, "error -- ");
  825.     }
  826.     fprintf (stderr, fmt, arg1, arg2, arg3);
  827.     if (flags & SYS) {
  828.         perror ("");
  829.     } else {
  830.         fprintf (stderr, "\n");
  831.     }
  832.     (void) fflush (stderr);
  833.     if (flags & FATAL) {
  834.         exit (1);
  835.     }
  836. }
  837.  
  838. /*
  839.  * Called from dodelete() to warn the user about files that were
  840.  * to be deleted, but which were not in the archive.
  841.  */
  842.  
  843. static void notfound ()
  844. {
  845.     register ARFILE *lp;
  846.  
  847.     DBUG_ENTER ("notfound");
  848.     for (lp = list; lp != NULL; lp = lp -> l_next) {
  849.         if (!lp -> l_flag) {
  850.             Error (WARN|SYS, "can't delete '%s'", lp -> l_arname);
  851.             if (logging) {
  852.                 fprintf (stderr, "Can't delete \"%s\" -- not found\n",
  853.                         lp -> l_arname);
  854.             }
  855.         }
  856.     }
  857.     DBUG_VOID_RETURN;
  858. }
  859.  
  860. /*
  861.  * If text is null, read a record, returning TRUE if text contains a header.
  862.  * Parse the header into arname.
  863.  */
  864.  
  865. static int gethdr (fd)
  866. FILE *fd;
  867. {
  868.     register char *tp;
  869.     register char *np;
  870.  
  871.     DBUG_ENTER ("gethdr");
  872.     if (text[0] == EOS && fgets (text, (int) sizeof (text), fd) == NULL) {
  873.         DBUG_RETURN (FALSE);
  874.     }
  875.     if (text[0] != '-' || text[1] != 'h' || text[2] != '-') {
  876.         DBUG_RETURN (FALSE);
  877.     }
  878.     for (tp = &text[3]; *tp && *tp <= ' '; tp++);
  879.     for (np = &arname[0]; *tp > ' '; *np++ = *tp++);
  880.     *np = EOS;
  881.     DBUG_RETURN (TRUE);
  882. }
  883.  
  884. /*
  885.  * If name is in the list, mark it as "found" and return TRUE.
  886.  * If true, and fname is not NULL, fname will have the file argument.
  887.  */
  888.  
  889. static int findarg (name, fname)
  890. char *name;
  891. char *fname;                    /* Gets full file name           */
  892. {
  893.     register ARFILE *lp;
  894.  
  895.     DBUG_ENTER ("findarg");
  896.     if ((lp = list) == NULL) {
  897.         if (fname != NULL) {
  898.             fname[0] = '\000';
  899.         }
  900.         DBUG_RETURN (TRUE);
  901.     }
  902.     while (lp != NULL) {
  903.         if (match (name, lp -> l_arname)) {
  904.             lp -> l_flag = TRUE;
  905.             if (fname != NULL) {
  906.                 if (lp -> l_filename == NULL) {
  907.                     fname[0] = EOS;
  908.                 } else {
  909.                     (void) strcpy (fname, lp -> l_filename);
  910.                 }
  911.             }
  912.             DBUG_RETURN (TRUE);
  913.         }
  914.         lp = lp -> l_next;
  915.     }
  916.     DBUG_RETURN (FALSE);
  917. }
  918.  
  919. /*
  920.  * Compare strings (note: case insensitive)
  921.  */
  922.  
  923. static int compare (string1, string2)
  924. register char *string1;
  925. register char *string2;
  926. {
  927.     DBUG_ENTER ("compare");
  928.     while (tolower (*string1) == tolower (*string2)) {
  929.         if (*string1 == NULL) {
  930.             DBUG_RETURN (0);
  931.         }
  932.         string1++;
  933.         string2++;
  934.     }
  935.     DBUG_RETURN ((tolower (*string1) > tolower (*string2)) ? 1 : -1);
  936. }
  937.  
  938. /*
  939.  * Add file "fname" (archive element "name") to the archive
  940.  */
  941.  
  942. static int addfile (name, fname, outfd, ecount, why)
  943. char *name;                     /* Archive element name */
  944. char *fname;                    /* Archive file name */
  945. FILE *outfd;                    /* Output file, already open */
  946. int ecount;                     /* Current error count (updated */
  947. char *why;                      /* Why are we here -- for verbosity */
  948. {
  949.     register FILE *infd;
  950.  
  951.     DBUG_ENTER ("addfile");
  952.     if ((infd = fopen (fname, "r")) == NULL) {
  953.         Error (WARN|SYS, "'%s' archive member '%s' not found", why,
  954.                 (fname == NULL) ? "{Null}" : fname);
  955.         if (logging) {
  956.             fprintf (stderr, "%s archive member \"%s\" not found\n", why,
  957.                     (fname == NULL) ? "{Null}" : fname);
  958.         }
  959.         ecount++;
  960.     } else {
  961. #ifdef DECUS
  962.         fgetname (infd, filename);
  963. #else
  964.         (void) strcpy (filename, fname);
  965. #endif
  966.         if (verbose) {
  967.             fprintf (logfd, "%s archive member \"%s\" (%s)\n", why, name,
  968.                         filename);
  969.         }
  970.         fprintf (outfd, "-h- %s\t%s\t%s\n", name, timetext, filename);
  971.         arimport (infd, outfd);
  972.         (void) fclose (infd);
  973.     }
  974.     DBUG_RETURN (ecount);
  975. }
  976.  
  977. /*
  978.  * Get file name, stripping off device:[directory] and ;version.
  979.  * The archive name ("FILE.EXT" is written to outname, while the
  980.  * full file name is written to outfilename.  On a dec operating system,
  981.  * outname is forced to lowercase.
  982.  */
  983.  
  984. static void argetname (fd, outname, outfilename)
  985. FILE *fd;
  986. char *outname;                  /* Archive name */
  987. char *outfilename;              /* Full file name */
  988. {
  989.     register char *tp;
  990. #ifndef unix
  991.     char bracket;
  992. #endif
  993.     extern char *strrchr ();
  994.  
  995.     DBUG_ENTER ("argetname");
  996.     fgetname (fd, outfilename);
  997.     (void) strcpy (outname, outfilename);
  998. #ifdef  unix
  999.     /* 
  1000.      * outname is after all directory information
  1001.      */
  1002.     if ((tp = strrchr (outname, '/')) != NULL) {
  1003.         (void) strcpy (outname, tp + 1);
  1004.     }
  1005. #else
  1006.     if ((tp = strrchr (outname, ';')) != NULL) {
  1007.         *tp = EOS;
  1008.     }
  1009.     while ((tp = strchr (outname, ':')) != NULL) {
  1010.         (void) strcpy (outname, tp + 1);
  1011.     }
  1012.     switch (outname[0]) {
  1013.         case '[': 
  1014.             bracket = ']';
  1015.             break;
  1016.         case '<': 
  1017.             bracket = '>';
  1018.             break;
  1019.         case '(': 
  1020.             bracket = ')';
  1021.             break;
  1022.         default: 
  1023.             bracket = EOS;
  1024.             break;
  1025.     }
  1026.     if (bracket != EOS) {
  1027.         if ((tp = strchr (outname, bracket)) == NULL) {
  1028.             Error (WARN, "? Illegal file name '%s'", outfilename);
  1029.         } else {
  1030.             (void) strcpy (outname, tp + 1);
  1031.         }
  1032.     }
  1033.     for (tp = outname; *tp != EOS; tp++) {
  1034.         *tp = tolower (*tp);
  1035.     }
  1036. #endif
  1037.     DBUG_VOID_RETURN;
  1038. }
  1039.  
  1040.  
  1041. /*
  1042.  * "Rename" inname to outname the hard way.
  1043.  */
  1044.  
  1045. static void filemove (inname, outname)
  1046. char *inname;
  1047. char *outname;
  1048. {
  1049.     register FILE *infd;
  1050.     register FILE *outfd;
  1051.     long int nrecords;
  1052.  
  1053.     DBUG_ENTER ("filemove");
  1054.     if (verbose) {
  1055.         fprintf (logfd, "Copying %s to %s\n", inname, outname);
  1056.     }
  1057.     if ((infd = fopen (inname, "r")) == NULL) {
  1058.         Error (ERR|FATAL|SYS, "can't open '%s' for input", inname);
  1059.     }
  1060.     if ((outfd = fopen (outname, "w")) == NULL) {
  1061.         Error (ERR|FATAL|SYS, "can't open '%s' for write", outname);
  1062.     }
  1063.     for (nrecords = 0; fgets (text, (int) sizeof (text), infd) != NULL; nrecords++) {
  1064.         (void) fputs (text, outfd);
  1065.     }
  1066. #ifdef DECUS
  1067.     fgetname (infd, text);
  1068. #else
  1069.     (void) strcpy (text, inname);
  1070. #endif
  1071.     (void) fclose (infd);
  1072.     (void) fclose (outfd);
  1073.     if (delete (text) == -1) {
  1074.         Error (WARN|SYS, "can't delete '%s'", text);
  1075.     }
  1076.     if (verbose) {
  1077.         fprintf (logfd, "Archive %s contains %ld records.\n", outname,
  1078.                         nrecords);
  1079.     }
  1080.     DBUG_VOID_RETURN;
  1081. }
  1082.  
  1083. /*
  1084.  * Copy (or skip if outfd == NULL) to next header
  1085.  */
  1086.  
  1087. static void arcopy (infd, outfd)
  1088. register FILE *infd;
  1089. register FILE *outfd;
  1090. {
  1091.     DBUG_ENTER ("arcopy");
  1092.     while (fgets (text, (int) sizeof (text), infd) != NULL) {
  1093.         if (text[0] == '-' && text[1] != '-') {
  1094.             DBUG_VOID_RETURN;
  1095.         }
  1096.         if (outfd != NULL) {
  1097.             (void) fputs (text, outfd);
  1098.         }
  1099.     }
  1100.     text[0] = EOS;              /* EOF signal            */
  1101.     DBUG_VOID_RETURN;
  1102. }
  1103.  
  1104. /*
  1105.  * Import text, writing it in the secret ar format.
  1106.  */
  1107.  
  1108. static void arimport (infd, outfd)
  1109. register FILE *infd;
  1110. register FILE *outfd;
  1111. {
  1112.     DBUG_ENTER ("arimport");
  1113.     while (fgets (text, (int) sizeof (text), infd) != NULL) {
  1114.         if (text[0] == '-') {
  1115.             (void) putc ('-', outfd);                   /* Quote         */
  1116.         }
  1117.         (void) fputs (text, outfd);
  1118.     }
  1119.     DBUG_VOID_RETURN;
  1120. }
  1121.  
  1122. /*
  1123.  * Read secret archive format, writing archived data to outfd.
  1124.  * Clean out extraneous <cr>,<lf>'s
  1125.  */
  1126.  
  1127. static void arexport (infd, outfd)
  1128. register FILE *infd;
  1129. register FILE *outfd;
  1130. {
  1131.     register char  *tp;
  1132.  
  1133.     DBUG_ENTER ("arexport");
  1134.     while (fgets (text, (int) sizeof (text), infd) != NULL) {
  1135.         tp = &text[strlen (text)];
  1136.         if (tp > &text[1] && *--tp == '\n' && *--tp == '\r') {
  1137.             *tp++ = '\n';
  1138.             *tp = EOS;
  1139.         }
  1140.         if (text[0] == '-') {
  1141.             if (text[1] != '-') {
  1142.                 DBUG_VOID_RETURN;
  1143.             }
  1144.             (void) fputs (text + 1, outfd);
  1145.         } else {
  1146.             (void) fputs (text, outfd);
  1147.         }
  1148.     }
  1149.     text[0] = EOS;
  1150.     DBUG_VOID_RETURN;
  1151. }
  1152.  
  1153. /*
  1154.  * Pattern match between
  1155.  *      name    string argument (FILE.EXT format)
  1156.  *      pattern which may contain wildcards.
  1157.  *
  1158.  * Note: '*' matches all but '.' separator between file and ext.
  1159.  *      '?' matches one character, but not '.'
  1160.  *
  1161.  */
  1162.  
  1163. typedef struct filename {
  1164.     char namepart[10];
  1165.     char typepart[4];
  1166. } FILENAME;
  1167.  
  1168. static int match (name, pattern)
  1169. register char *name;
  1170. register char *pattern;
  1171. {
  1172.     FILENAME namebuff;
  1173.     FILENAME patternbuff;
  1174.     int result;
  1175.  
  1176.     DBUG_ENTER ("match");
  1177.     if (breakout (name, &namebuff) || breakout (pattern, &patternbuff)) {
  1178.         result = FALSE;
  1179.     } else {
  1180.         result = (match1 (namebuff.namepart, patternbuff.namepart)
  1181.             && match1 (namebuff.typepart, patternbuff.typepart));
  1182.     }
  1183.     DBUG_RETURN (result);
  1184. }
  1185.  
  1186. /*
  1187.  * Parse arg ("foo.bar") into "foo" and "bar"
  1188.  * Return TRUE if trouble.
  1189.  */
  1190.  
  1191. static int breakout (arg, buff)
  1192. char *arg;
  1193. FILENAME * buff;
  1194. {
  1195.     register char *ap;
  1196.     register char *bp;
  1197.     register int dotseen;
  1198.     int size;
  1199.  
  1200.     DBUG_ENTER ("breakout");
  1201.     dotseen = FALSE;
  1202.     ap = arg;
  1203.     bp = buff -> namepart;
  1204.     buff -> typepart[0] = EOS;
  1205.     size = (sizeof buff -> namepart) - 1;
  1206.     while (*ap != EOS) {
  1207.         if (*ap == '.') {
  1208.             if (dotseen++) {                    /* 2 dots        */
  1209.                 DBUG_RETURN (TRUE);
  1210.             } else {
  1211.                 ap++;
  1212.                 *bp = EOS;
  1213.                 bp = buff -> typepart;
  1214.                 size = (sizeof buff -> typepart) - 1;
  1215.                 continue;
  1216.             }
  1217.         }
  1218.         if (size-- <= 0) {                      /* 2 big         */
  1219.             DBUG_RETURN (TRUE);
  1220.         }
  1221.         *bp++ = *ap++;
  1222.     }
  1223.     *bp = EOS;
  1224.     DBUG_RETURN (FALSE);
  1225. }
  1226.  
  1227. /*
  1228.  * Recursive routine to match "name" against "pattern".
  1229.  * Returns TRUE if successful.
  1230.  */
  1231.  
  1232. static int match1 (name, pattern)
  1233. register char *name;            /* What to look for */
  1234. register char *pattern;         /* May have wildcard */
  1235. {
  1236.     register char pattbyte;
  1237.     char namebyte;
  1238.  
  1239.     DBUG_ENTER ("match1");
  1240.     for (;;) {
  1241.         /* 
  1242.          * First check for pattern ending in '*' -- this has to succeed
  1243.          */
  1244.         if ((pattbyte = *pattern++) == '*' && *pattern == EOS) {
  1245.             DBUG_RETURN (TRUE);
  1246.         }
  1247.         /* 
  1248.          * If not, then if both strings finish equally, it succeeds.
  1249.          */
  1250.         if ((namebyte = *name) == EOS && pattbyte == EOS) {
  1251.             DBUG_RETURN (TRUE);
  1252.         }
  1253.         /* 
  1254.          * Not at end of the name string.
  1255.          */
  1256.         switch (pattbyte) {
  1257.             case EOS:           /* End of pattern -> failure     */
  1258.                 DBUG_RETURN (FALSE);
  1259.             case '*':           /* Wild card means "advance"     */
  1260.                 do {
  1261.                     if (match1 (name, pattern)) {
  1262.                         DBUG_RETURN (TRUE);
  1263.                     }
  1264.                 } while (*name++ != EOS);
  1265.                 DBUG_RETURN (FALSE);    /* Did our best                  */
  1266.             default: 
  1267.                 if (tolower (namebyte) != tolower (pattbyte)) {
  1268.                     DBUG_RETURN (FALSE);
  1269.                 }
  1270.             case '?':           /* One byte joker                */
  1271.             case '%':           /* RT11 one byte joker           */
  1272.                 name++;         /* Matched this one              */
  1273.         }
  1274.     }
  1275. }
  1276.  
  1277. #ifdef  FAKEFWILD
  1278.  
  1279. /* Set if a file is open         */
  1280. /*  0   nothing open             */
  1281. /* +1   open, fnext not called   */
  1282. /* +2   fnext called once        */
  1283. static int  fake_flag = 0;
  1284.  
  1285. static char fake_name[81];      /* Name of file */
  1286.  
  1287. /*
  1288.  * "setup" to open a wildcard file name
  1289.  */
  1290.  
  1291. static FILE *fwild (fname, mode)
  1292. char *fname;
  1293. char *mode;
  1294. {
  1295.     register FILE *fd;
  1296.  
  1297.     DBUG_ENTER ("fwild");
  1298.     if (fake_flag != 0) {
  1299.         Error (WARN, "fwild/fnext out of sync");
  1300.     }
  1301.     fake_flag = 0;
  1302.     if ((fd = fopen (fname, mode)) != NULL) {
  1303.         fake_flag++;
  1304.         (void) strcpy (fake_name, fname);
  1305.     }
  1306.     DBUG_RETURN (fd);
  1307. }
  1308.  
  1309. static FILE *fnext (fd)
  1310. FILE *fd;
  1311. {
  1312.     DBUG_ENTER ("fnext");
  1313.     switch (fake_flag) {
  1314.         case 1: 
  1315.             fake_flag++;        /* First call after fwild        */
  1316.             DBUG_RETURN (fd);   /* File is "open"                */
  1317.         case 2: 
  1318.             fake_flag = 0;      /* Second call of fnext          */
  1319.             (void) fclose (fd); /* Close existing file           */
  1320.             fake_name[0] = EOS; /* Zap file name                 */
  1321.             DBUG_RETURN ((FILE *)NULL); /* No more files left            */
  1322.         default: 
  1323.             Error (WARN, "fnext called without calling fwild");
  1324.             DBUG_RETURN ((FILE *) NULL);
  1325.     }
  1326. }
  1327.  
  1328. /*
  1329.  *      Note, this only works for files opened via fwild/fnext. (fnf)
  1330.  */
  1331.  
  1332. static void fgetname (fd, name)
  1333. FILE *fd;
  1334. char *name;
  1335. {
  1336.     if (fd != NULL) {
  1337.         (void) strcpy (name, fake_name);
  1338.     }
  1339. }
  1340.  
  1341. #endif  /* FAKEFWILD */
  1342.  
  1343. #ifndef unix
  1344. perror (sp)
  1345. char *sp;
  1346. {
  1347.     if (sp != NULL) {
  1348.         fprintf (stderr, "%s", sp);
  1349.     }
  1350.     fprintf (stderr, ": <unknown error>");
  1351. }
  1352. #endif
  1353.  
  1354. #ifdef AMIGA
  1355.  
  1356. int delete (name)
  1357. char *name;
  1358. {
  1359.     int status;
  1360.     extern int DeleteFile ();
  1361.     
  1362.     DBUG_ENTER ("delete");
  1363.     if (DeleteFile (name)) {            /* Returns 0 or 1 */
  1364.         status = 0;                     /* Success */
  1365.     } else {
  1366.         status = -1;                    /* Failure */
  1367.     }
  1368.     DBUG_RETURN (status);
  1369. }
  1370.  
  1371. #endif  /* AMIGA */
  1372.  
  1373. /* 
  1374.  * Setup the time of day, erasing trailing '\n'
  1375.  */
  1376.  
  1377. #ifdef AMIGA
  1378. #  undef TRUE   /* TRUE, FALSE, and NULL are all redefined in types.h, */
  1379. #  undef FALSE  /* which is ultimately pulled in.  They have the same */
  1380. #  undef NULL   /* numeric values but are not enclosed in parens. */
  1381. #  include <libraries/dosextens.h>
  1382. #  include <libraries/dos.h>
  1383. #endif
  1384.  
  1385. static char *GetTime ()
  1386. {
  1387. #ifdef unix
  1388.     register char *cp;
  1389.     register char *now;
  1390.     long timval;
  1391.     extern long time ();
  1392.     extern char *ctime ();
  1393.  
  1394.     (void) time (&timval);
  1395.     now = ctime (&timval);
  1396.     cp = now + strlen (now);
  1397.     while (cp > now && *--cp <= ' ');
  1398.     cp[1] = EOS;
  1399.     return (now);
  1400. #else
  1401. #ifdef AMIGA
  1402.     auto struct DateStamp now;
  1403.     static char buffer[64];
  1404.     extern struct DateStamp *DateStamp ();
  1405.  
  1406.     if (DateStamp (&now) == NULL) {
  1407.         Error (WARN|SYS, "can't get current date");
  1408.         now.ds_Days = 0;
  1409.         now.ds_Minute = 0;
  1410.         now.ds_Tick = 0;
  1411.     }
  1412.     /* Kinda ugly for now, just dump structure as ascii. */
  1413.     sprintf (buffer, "%u:%u:%u", now.ds_Days, now.ds_Minute, now.ds_Tick);
  1414.     return (buffer);
  1415. #else
  1416.     return ("<time currently unavailable>");
  1417. #endif  /* AMIGA */
  1418. #endif  /* unix */
  1419. }
  1420.  
  1421.